文章同步發表至 Medium
縣市找景點的完整功能描述如下:
- 身為一個使用者,我可以點擊地圖上的查詢按鈕,選擇縣市之後找到該縣市的景點。
- 身為一個使用者,查詢縣市景點之後,地圖上只會呈現該縣市的景點。
從後端的角度來看,第一點是我們要做到的事情,所以首先,我們來檢視一下資料庫裡有沒有儲存到相關的縣市資訊:
這是我們在前幾天寫出來的 API,直接撈取資料庫的所有資料然後回傳——看起來除了 XY 座標和 address
之外,我們沒有任何和縣市有關的資訊。假設今天我們不只要做縣市,還需要細分到鄉鎮市區的時候,純文字、沒有固定格式的 address
,要如何特別找到綠島鄉呢?
因此,我們要從資料庫裏面的 Geometry 著手,利用縣市的範圍找到交集,決定哪些景點在使用者選取的縣市內。
這個時候,政府資料開放平台就派上用場了。裡面由國土測繪中心所提供的直轄市、縣市界線是一個很好用的素材,可以讓我們藉由讀取檔案,找到縣市的 Geometry,接著再利用 NetTopologySuite 去針對我們資料庫裡的資料作處理。
在使用之前要注意:我們的資料所儲存的座標系統是 4326,國土測繪中心所提供的是 以 TWD97 為基準的 Shapefile,所以要先使用 QGIS Export Feature As 的功能,匯出成 4326 再繼續使用喔。
步驟很簡單,讀取縣市界線的 Shapefile,找到的話就和景點的清單做比較,如果景點在這個縣市裡(Within),就撈出來:
/// <summary> 縣市裡的景點清單 </summary>
/// <param name="county">縣市代碼</param>
[HttpGet("{county}")]
public IActionResult ScenicSpotInCounty(string county)
{
// 建立一個空的 Geometry
Geometry? countyGeometry = null;
// 讀取 Shapefile
foreach (var feature in Shapefile.ReadAllFeatures(@"C:\Users\user\Downloads\mapdata202209220943\COUNTY_MOI_1090820_4326.shp"))
{
// 如果 COUNTYID 的欄位等於傳入的縣市代碼則記錄下這筆 Geometry
if (feature.Attributes["COUNTYID"].ToString() == county)
{
countyGeometry = feature.Geometry;
break;
}
}
// 處理找不到縣市代碼的情況
if (countyGeometry == null) throw new KeyNotFoundException($"找不到代碼為 {county} 的縣市");
var data = _db.ScenicSpots
.Where(s => s.Geom.Within(countyGeometry))
.Select(s => new ScenicSpotInfo()
{
Id = s.Id,
Name = s.Name,
Telephone = s.Telephone ?? "",
Address = s.Address ?? "",
X = s.Geom.Centroid.X,
Y = s.Geom.Centroid.Y,
Type = s.Type ?? "未分類",
Park = s.Park ?? "",
Info = s.Info ?? "暫無詳細資訊",
UpdateTime = s.UpdateTime
}).ToList();
return Ok(data);
}
如果比較喜歡使用 Contains 的話,請記得把第 24 行修改一下喔:
.Where(s => countyGeometry.Contains(s.Geom))